package com.jxmobi.util.file;

import com.jxmobi.util.algorithm.AlgorithmUtils;
import com.jxmobi.util.ftp.FtpEnvConfig;
import com.jxmobi.util.ftp.FtpFileType;
import com.jxmobi.util.ftp.FtpSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.Stream;

/**
 *
 * @author Xiaofei Chen <a href="mailto:xchen@jxmobi.com">Email the author</a>
 * @version 1.0 5/25/16
 */
public final class FileSplitterUtil {
    private static final Logger log = LoggerFactory.getLogger(FileSplitterUtil.class);

    private static FileSplitterUtil instance = new FileSplitterUtil();

    private static long DEFAULT_BULK_SIZE = 1020;

    private FileSplitterUtil() {

    }

    public static void main(String[] args) throws IOException {
        File[] files = readAllFiles("sample", true);

        for (File file : files) {
            System.out.println(file.getPath());
            System.out.println(file.getAbsoluteFile());
        }

        FtpSender.configure(new FtpEnvConfig("test.bimayun.com", "pwftp", "Qwerty555", FtpFileType.BINARY, "v1.2.4"));

//        readAllFileStream(System.getProperty("user.dir"), false)
//                .filter(file -> file.getPath().contains("packet"))
//                .sorted(Comparator.comparing(File::getName))
//                .forEach(file -> {
//
//                    FtpSender.sendFile(file.getPath(), "v1.2.1/" + file.getName(), false);
//
//                    System.out.println(file.getPath());
//                    System.out.println(file.getName());
//
//                    byte[] data = read(file.getPath(), false);
//
//                    ByteBuffer byteBuffer = ByteBuffer.wrap(data);
//                    byteBuffer.position(data.length - 4);
//
//                    int crcCode = (int) byteBuffer.getChar();
//                    int length = (int) byteBuffer.getChar();
//
//                    log.info(String.format("packet length #%d, with crcCode=%d", length, crcCode));
//
//                });
        String originFile = "tcu_bin/TCUv1-2-0.bin";
        FileSplitterUtil.setSpiltSize(1020).splitAndCrc16(originFile);

        int crc16Code = FileSplitterUtil.crc16(originFile, true);
        System.out.printf("CRC16 code of %s is %d", originFile, crc16Code);

        Stream<File> fileStream = readAllFileStream(System.getProperty("user.dir"), false)
                .filter(file -> file.getPath().contains("packet"))
                .sorted(Comparator.comparing(File::getName));
        FtpSender.batchSendFilesOneLevel(fileStream, "v1.2.3", false);


    }

    /**
     * 获得指定文件的绝对路径
     * @param file 给定的文件名，为相对路径
     * @param resource 是否采用资源路路径，
     *                 true，如果文件路径为相对与resources目录的路径;
     *                 false，如果文件路径为绝对目录
     * @return 文件完整路径
     */
    public static String retrieveResourcePath(String file, boolean resource)  {
        URL fileUrl = null;
        try {
            fileUrl = resource ? FileSplitterUtil.class.getClassLoader().getResource(file) : Paths.get(file).toUri().toURL();
        } catch (MalformedURLException e) {
            log.error(String.format("给定的地址%s不合法", file));
            e.printStackTrace();
        }
        return fileUrl.getPath();
    }

    /**
     *  计算指定文件的 crc16 码
     * @param file 给定的文件名（<strong>相对路径文件名</strong>）
     * @param resource 是否为资源文件
     *                 是， 从 maven 结构的 resources 文件夹寻找文件
     *                 否， 从 user.dir 定义的文件夹起始，查找指定文件
     * @return crc16 码
     */
    public static int crc16(String file, boolean resource) {
        byte[] data = read(file, resource);

        return AlgorithmUtils.crc16_8005(data);
    }

    public static int crc16(byte[] data) {
        return AlgorithmUtils.crc16_8005(data);
    }



    /**
     *  链式设置分割文件的大小
     * @param size 设置默认的文件切割的大小
     * @return 设置好的 instance
     */
    public static FileSplitterUtil setSpiltSize(int size) {
        if (size < 0)
            throw new IllegalArgumentException("切割的文件的大小必须大于0");

        DEFAULT_BULK_SIZE = size;

        return instance;
    }

    /**
     *  将给定的文件切割为 n / (n+1) 块
     * @param file 待切割的文件
     * @param partsNumber 切割的文件数目
     * @throws IOException
     */
    public static void split(String file, int partsNumber) throws IOException {
        URL fileUrl = FileSplitterUtil.class.getClassLoader().getResource(file);

        RandomAccessFile raf = new RandomAccessFile(fileUrl.getPath(), "r");
        long sourceSize = raf.length();

        long bytesPerSplit = sourceSize/ partsNumber ;
        System.out.println(sourceSize);

        log.debug(String.format(" going to split the file [%s] with size #%d bytes# into %d parts", file, sourceSize, partsNumber));
        repeatWrite(raf, bytesPerSplit);

    }

    /**
     *  将指定的文件按照一定的大小切割
     * @param file 指定的文件名
     */
    public static void split(String file) {
        try {
            URL fileUrl = FileSplitterUtil.class.getClassLoader().getResource(file);
            System.out.println(fileUrl.getPath());

            RandomAccessFile raf = new RandomAccessFile(fileUrl.getPath(), "r");

            log.debug(String.format(" going to split the file [%s] with size #%d bytes# into %d size packets", file, raf.length(), DEFAULT_BULK_SIZE));

            repeatWrite(raf, DEFAULT_BULK_SIZE);
        } catch (FileNotFoundException e) {
            log.error(String.format(" cannot find the file specified: %s", file));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     *  获取指定文件路径下面的所有文件（不包括文件夹），并以文件数组的形式返回
     * @param pathName 文件路径
     * @param resource 是否采用资源路路径，true，如果文件路径为相对与resources目录的路径;false，如果文件路径为绝对目录
     * @return 文件数组
     */
    public static File[] readAllFiles(String pathName, boolean resource) {
        File[] files = null;

        String fullPath = retrieveResourcePath(pathName, resource);
        log.info(String.format("full path of %s is %s", pathName, fullPath));

        try {
            files = Files.walk(Paths.get(fullPath))
                    .filter(Files::isRegularFile)
                    .map(Path::toFile)
                    .toArray(File[]::new);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return files;
    }

    /**
     *  获取指定文件路径下面的所有文件（不包括文件夹），并以文件Stream的形式返回
     * @param pathName 文件路径
     * @param resource 是否采用资源路路径，
     *                 true，如果文件路径为相对与resources目录的路径;
     *                 false，如果文件路径为绝对目录
     * @return 文件Stream
     */
    public static Stream<File> readAllFileStream(String pathName, boolean resource) {
        String fullPath = retrieveResourcePath(pathName, resource);
        log.info(String.format("full path of %s is %s", pathName, fullPath));

        try {
            return Files.list(Paths.get(fullPath))
                    .filter(Files::isRegularFile)
                    .map(Path::toFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     *  获取指定文件路径下面的所有文件（不包括文件夹），并以文件Stream的形式返回
     * @param pathName 文件路径
     * @param resource 是否采用资源路路径，
     *                 true，如果文件路径为相对与resources目录的路径;
     *                 false，如果文件路径为绝对目录
     * @return 文件Stream
     */
    public static Stream<File> readAllFileStreamRecursively(String pathName, boolean resource) {
        String fullPath = retrieveResourcePath(pathName, resource);
        log.info(String.format("full path of %s is %s", pathName, fullPath));

        try {
            return Files.walk(Paths.get(fullPath))
                    .filter(Files::isRegularFile)
                    .map(Path::toFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     *  将给定的文件的字节数组返回
     * @param file 相对路径文件名
     * @param resource 是否采用资源路路径，
     *                 true，如果文件路径为相对与resources目录的路径;
     *                 false，如果文件路径为绝对目录
     * @return 字节数组
     */
    public static byte[] read(String file, boolean resource) {
        String fullFilePath = retrieveResourcePath(file, resource);

        Path path = Paths.get(fullFilePath);

        byte[] data = null;
        try {
            data = Files.readAllBytes(path);
        } catch (IOException e) {
            log.error("file read error occurs: " + e.getMessage());
            return null;
        }

        return data;
    }

    /**
     *  将给定的文件的字节数组返回
     * @param file 文件对象
     * @return 字节数组
     */
    public static byte[] read(File file) {

        Path path = Paths.get(file.toURI());

        byte[] data = null;
        try {
            data = Files.readAllBytes(path);
        } catch (IOException e) {
            log.error("file read error occurs: " + e.getMessage());
            return null;
        }

        return data;
    }


    /**
     *  将指定的文件按照一定的大小切割,并在最后添加4个字节，前两个字节为该文件的大小（字节为单位），后两个字节为 crc16 码
     *  注意： 分割的文件直接创建在当前工作目录下
     * @param file 指定的文件名
     */
    public static void splitAndCrc16(String file) {
        try {
            String absolutePath = retrieveResourcePath(file, true);

            RandomAccessFile raf = new RandomAccessFile(absolutePath, "r");

            log.info(String.format(" going to split the file [%s] with size #%d bytes# into %d size packets", file, raf.length(), DEFAULT_BULK_SIZE));

            repeatWriteUsingCrc16(raf, DEFAULT_BULK_SIZE, "");
        } catch (FileNotFoundException e) {
            log.error(String.format(" cannot find the file specified: %s", file));
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     *  将指定的文件按照一定的大小切割,并在最后添加4个字节，前两个字节为该文件的大小（字节为单位），后两个字节为 crc16 码
     *  注意： 分割的文件直接创建在当前工作目录下的 parentPath 下
     *
     * @param file 待分割的文件名（该文件放置在 resources 目录下）
     * @param parentPath 分割的文件放置的<strong>相对目录</strong>，
     *                   如果该目录不存在，则直接创建;
     *                   如果该目录存在，直接将分割后的文件放在该目录下面
     * @param resource if or not as resource path
     */
    public static int splitAndCrc16(String file, String parentPath, boolean resource){
        try {
            String absolutePath = retrieveResourcePath(file, resource);

            RandomAccessFile raf = new RandomAccessFile(absolutePath, "r");

            log.info(String.format(" going to split the file [%s] with size #%d bytes# into %d size packets", file, raf.length(), DEFAULT_BULK_SIZE));

            return repeatWriteUsingCrc16(raf, DEFAULT_BULK_SIZE, parentPath);
        } catch (FileNotFoundException e) {
            log.error(String.format(" cannot find the file specified: %s", file));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return -1;

    }

    public static void splitAndCrc16(byte[] data, String parentPath) {
        ByteBuffer buf = ByteBuffer.wrap(data);

        int totalPieces = (int) (buf.capacity() / DEFAULT_BULK_SIZE + 1);

        for (int i = 0; i < totalPieces; i++) {
            buf.position((int) (i * DEFAULT_BULK_SIZE));
            buf.limit((int) ((i + 1) * DEFAULT_BULK_SIZE));



        }


//        try {
//            String absolutePath = retrieveResourcePath(file, true);
//
//            RandomAccessFile raf = new RandomAccessFile(absolutePath, "r");
//
//            log.info(String.format(" going to split the file [%s] with size #%d bytes# into %d size packets", file, raf.length(), DEFAULT_BULK_SIZE));
//
//            repeatWriteUsingCrc16(raf, DEFAULT_BULK_SIZE, parentPath);
//        } catch (FileNotFoundException e) {
//            log.error(String.format(" cannot find the file specified: %s", file));
//        } catch (IOException e) {
//            e.printStackTrace();
//        }

    }

    private static void repeatWrite(RandomAccessFile raf, long bytesPerSplit) throws IOException {
        int maxReadBufferSize = 8 * 1024; //1KB

        long sourceSize = raf.length();

        int numSplits = (int) (sourceSize / bytesPerSplit);

        long remainingBytes = sourceSize % bytesPerSplit;

        for(int destIx=1; destIx <= numSplits; destIx++) {
            BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream("packet."+destIx));
            if(bytesPerSplit > maxReadBufferSize) {
                long numReads = bytesPerSplit/maxReadBufferSize;
                long numRemainingRead = bytesPerSplit % maxReadBufferSize;
                for(int i=0; i<numReads; i++) {
                    readWrite(raf, bw, maxReadBufferSize);
                }
                if(numRemainingRead > 0) {
                    readWrite(raf, bw, numRemainingRead);
                }
            }else {
                readWrite(raf, bw, bytesPerSplit);
            }
            bw.close();
        }

        if(remainingBytes > 0) {
            BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream("packet."+(numSplits+1)));
            readWrite(raf, bw, remainingBytes);
            bw.close();
        }
        raf.close();
    }

    private static int repeatWriteUsingCrc16(RandomAccessFile raf, long bytesPerSplit, String parentPath) throws IOException {
        int maxReadBufferSize = 8 * 1024; //1KB

        String pathPrepend = "".equals(parentPath) ? "" : parentPath + File.separator;

        Path path = Paths.get(System.getProperty("user.dir") + File.separator + pathPrepend);
        if (!Files.exists(path)) {
            Files.createDirectories(path);
        }

        long sourceSize = raf.length();

        int numSplits = (int) (sourceSize / bytesPerSplit);

        long remainingBytes = sourceSize % bytesPerSplit;

        for(int destIx=1; destIx <= numSplits; destIx++) {

            String slice = String.format("packet%03d", destIx);

            BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(pathPrepend + slice));

            log.info(String.format("generating new packet named %s", slice));

            if(bytesPerSplit > maxReadBufferSize) {
                long numReads = bytesPerSplit/maxReadBufferSize;
                long numRemainingRead = bytesPerSplit % maxReadBufferSize;
                for(int i=0; i<numReads; i++) {
                    readWriteUsingCrc16(raf, bw, numRemainingRead);
                }
                if(numRemainingRead > 0) {
                    readWriteUsingCrc16(raf, bw, numRemainingRead);
                }
            }else {
                readWriteUsingCrc16(raf, bw, bytesPerSplit);
            }
            bw.close();
        }

        if(remainingBytes > 0) {
            String lastSlice = String.format("packet%03d", (numSplits+1));
            BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(pathPrepend + lastSlice));
            log.info(String.format("generating last packet named %s", lastSlice));
            readWriteUsingCrc16(raf, bw, remainingBytes);
            bw.close();
        }
        raf.close();

        return numSplits + 1;
    }

    private static void readWrite(RandomAccessFile raf, BufferedOutputStream bw, long numBytes) throws IOException {
        byte[] buf = new byte[(int) numBytes];
        int val = raf.read(buf);
        if(val != -1) {
            bw.write(buf);
        }
    }

    private static void readWriteUsingCrc16(RandomAccessFile raf, BufferedOutputStream bw, long numBytes) throws IOException {
        int _numBytes = (int) numBytes;
        int _fullBulkSize = Math.toIntExact(DEFAULT_BULK_SIZE + 4);
        int _defaultBulkSize = Math.toIntExact(DEFAULT_BULK_SIZE);

        byte[] _buf = new byte[_numBytes];
        byte[] _buf_full = new byte[_fullBulkSize];

        int val = raf.read(_buf);

        _buf_full = Arrays.copyOf(_buf, _buf_full.length);

        char crc16Code = (char) AlgorithmUtils.crc16_8005(_buf);
        char length = (char) _numBytes;

        log.info(String.format("packet length #%d, with crcCode=%d", (int) length, (int)crc16Code));

        ByteBuffer byteBuffer = ByteBuffer.wrap(_buf_full);
        byteBuffer.position(_defaultBulkSize);
        // little endian
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.putChar(crc16Code);
        byteBuffer.putChar(length);


        if(val != -1) {
            bw.write(_buf_full);
        }
    }

    private static void readWriteUsingCrc162(RandomAccessFile raf, BufferedOutputStream bw, long numBytes) throws IOException {
        int _numBytes = (int) numBytes;
        byte[] _buf = new byte[_numBytes];
        int val = raf.read(_buf);

        byte[] buf = Arrays.copyOf(_buf, _numBytes + 4);

        char crc16Code = (char) AlgorithmUtils.crc16_8005(_buf);
        char length = (char) _numBytes;

        log.info(String.format("packet length #%d, with crcCode=%d", (int) length, (int)crc16Code));

        ByteBuffer byteBuffer = ByteBuffer.wrap(buf);
        byteBuffer.position(_numBytes);
        // little endian, to suit for the tcu's requirement
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.putChar(crc16Code);
        byteBuffer.putChar(length);


        if(val != -1) {
            bw.write(buf);
        }
    }
}
